package com._21cn.cms.resource.utils;

import com._21cn.cms.common.Constants;
import com._21cn.cms.resource.config.PictureConfig;
import com._21cn.cms.resource.entity.Picture;
import com._21cn.cms.resource.service.PictureService;
import com._21cn.framework.utils.FileUtil;
import com._21cn.framework.utils.StringUtil;
import com._21cn.framework.utils.image.IMImageInfo;
import com._21cn.framework.utils.image.IMImageUtil;
import com._21cn.framework.utils.image.ImageUtil;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.gif4j.GifDecoder;
import com.gif4j.GifImage;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.*;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.*;
import org.im4java.core.InfoException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 图片相关参数配置
 *
 * @author chenjh
 */
@Component("pictureManager")
public class PictureManager {

    private static final Logger log = LoggerFactory.getLogger(PictureManager.class);

    private PictureConfig pictureConfig = new PictureConfig();          //图片配置项

    @Autowired
    private PictureService pictureService;

    private static HttpResponseInterceptor contentEncodingFixerInterceptor = new HttpResponseInterceptor()  {
        @Override
        public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
            Header contentEncodingHeader = response.getFirstHeader(HTTP.CONTENT_ENCODING);
            if(contentEncodingHeader != null && contentEncodingHeader.getValue().equalsIgnoreCase("none")) {
                response.removeHeaders(HTTP.CONTENT_ENCODING);
                response.addHeader(HTTP.CONTENT_ENCODING, "identity");
            }
        }
    };
    private static BasicHttpProcessor httpProcessor = new BasicHttpProcessor();
    static {
        httpProcessor.addInterceptor(contentEncodingFixerInterceptor);
        // Required protocol interceptors
        httpProcessor.addInterceptor(new RequestContent());
        httpProcessor.addInterceptor(new RequestTargetHost());
        httpProcessor.addInterceptor(new RequestDefaultHeaders());
        // Recommended protocol interceptors
        httpProcessor.addInterceptor(new RequestConnControl());
        httpProcessor.addInterceptor(new RequestUserAgent());
        httpProcessor.addInterceptor(new org.apache.http.client.protocol.RequestExpectContinue());

        // HTTP state management interceptors
        httpProcessor.addInterceptor(new RequestAddCookies());
        httpProcessor.addInterceptor(new ResponseProcessCookies());

        // HTTP authentication interceptors
        httpProcessor.addInterceptor(new RequestTargetAuthentication());
        httpProcessor.addInterceptor(new RequestProxyAuthentication());
    }
    private static RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(5000)
            .setConnectTimeout(1500).build();
    private static CloseableHttpClient httpClient = HttpClients.custom()
            .setHttpProcessor(httpProcessor)
            .setDefaultRequestConfig(defaultRequestConfig)
            .setUserAgent(getRandomUa())
            .setMaxConnTotal(10000)
            .setMaxConnPerRoute(3000)
            .build();

    /**
     * 处理图片文件
     *
     * @param p 图片对象
     */
    public Picture savePictureFile(Picture p) {

        if (p.getTitle() == null || StringUtils.isBlank(p.getTitle())) {
            log.error("picture title is null!");
            return null;
        }
        // 用MD4算法生成hashcode
        p.setHashcode(FileUtil.md4HashCode(p.getTempFile()));

        boolean isLostFile = false;
        // 在数据库中查hashcode看是否已经存在，存在就返回数据库中的数据
        Picture tempObj = pictureService.getByHashcode(p.getHashcode());
        StringBuilder tempObjPath = new StringBuilder(pictureConfig.getSaveFilePath());
        if (tempObj != null) {
            if (new File(tempObjPath.toString()).exists()) {
                p.getTempFile().delete();        // 删除临时文件
                p.setTempFile(null);
                p = tempObj;
                log.debug("same hashcode:" + p.getHashcode());
                return p;
            } else {
                tempObj.setTempFile(p.getTempFile());
                p = tempObj;
                isLostFile = true;
            }
        }

        // 设置图片域名
        if (StringUtil.isEmpty(p.getHost())) {
            p.setHost(pictureConfig.getDefaultHost());
        }

        // 设置图片站点归属
        if (p.getSiteId() == null || p.getSiteId() == 0) {
            p.setSiteId(pictureConfig.getDefaultSiteId());
        }

        if (p.getModifyUserId() == null || p.getModifyUserId().equals(0)) {
            p.setModifyUserId(p.getCreateUserId());
        }

        // 生成保存路径
        String filePath = null;
        String fileName = null;
        if (isLostFile) {
            filePath = tempObj.getPath();
            fileName = tempObj.getFileName();
        } else {
            filePath = new SimpleDateFormat("/yyyy/MMdd/HH/").format(p.getCreateTime());
            fileName = p.getHashcode().substring(0, 8) + "." + p.getExtName();
        }

        // 建立目录并复制临时文件到目标保存目录，最后删除临时文件
        File targetFile = new File( pictureConfig.getSaveFilePath() + p.getHost() + filePath + fileName );
        FileUtil.makeParentFolder(targetFile);

        p.setPath(filePath);
        // 设置原图文件名和缩略图文件名
        p.setFileName(fileName);

        // 获取原始文件大小和长宽
        IMImageInfo info = null;
        int imageWidth = 0;
        int imageHeight = 0;
        try {
            info = new IMImageInfo(p.getTempFile().getPath(), true, true);
            imageWidth = info.getImageWidth();
            imageHeight = info.getImageHeight();
            p.setExtName(info.getImageFormat().toLowerCase());
        } catch (InfoException e) {
            try {
                GifImage gifImage = GifDecoder.decode(p.getTempFile());
                imageWidth=gifImage.getCurrentLogicWidth();
                imageHeight=gifImage.getCurrentLogicHeight();
                p.setExtName("gif");
            } catch (Exception e2) {
                e2.printStackTrace();
                log.error(e2.getMessage());
                p.setTitle("获取图片信息失败！");
                p.setFileName(p.getTempFile().getName());
                p.getTempFile().delete();
                p.setTempFile(null);
                return p;
            }
        }
        int oSize = (int) p.getTempFile().length();

        // 判断是否超过文件大小和总像素的限制，超过限制就对图片做压缩处理
        boolean isExceedSize = oSize > pictureConfig.getMaxFileSize();
        boolean isExceedPixel = false;
        isExceedPixel = imageWidth * imageHeight > pictureConfig.getMaxPixelNumber();
        if (p.getExtName().equalsIgnoreCase("gif")){
            isExceedPixel=false;
            isExceedSize=false;
        } else if (info == null) {
            p.setTitle("获取图片信息失败！");
            p.setFileName(p.getTempFile().getName());
            p.getTempFile().delete();
            p.setTempFile(null);
            return p;
        }

        double angle = 0;
        if (p.getExtName().equalsIgnoreCase("jpg") || p.getExtName().equalsIgnoreCase("jpeg")) {
            angle = getNeededRotateAngle(p.getTempFile());
        }

        if (log.isDebugEnabled())
            log.debug("isExceedSize:" + isExceedSize + " isExceedPixel:" + isExceedPixel);

        if (isExceedSize || isExceedPixel || p.getExtName().equalsIgnoreCase("bmp")) {
            int[] wh = null;
            try {
                wh = ImageUtil.getRealWidthHeight(info.getImageWidth(), info.getImageHeight(), pictureConfig.getMaxWidth(), pictureConfig.getMaxHeight());
            } catch (InfoException e) {
                e.printStackTrace();
                log.error(e.getMessage());
                p.setTitle("图片处理失败！");
                p.setFileName(p.getTempFile().getName());
                p.getTempFile().delete();
                p.setTempFile(null);
                return p;
            }

            if (wh == null) {
                log.error("wh is null!");
                p.getTempFile().delete();
                p.setTempFile(null);
                return p;
            }

            if (log.isDebugEnabled())
                log.debug("w:" + wh[0] + " h:" + wh[1]);
            p.setWidth(wh[0]);
            p.setHeight(wh[1]);
            IMImageUtil im = new IMImageUtil();
            if (angle == 0) {
                oSize = (int) im.scale(p.getTempFile().getPath(), targetFile.getPath(), wh[0], wh[1]);
            } else {
                String tempRotatePath = "/tmp/rotate_" + UUID.randomUUID().toString();
                oSize = (int) im.scale(p.getTempFile().getPath(), tempRotatePath, wh[0], wh[1]);
                im.rotate(tempRotatePath, targetFile.getPath(), angle, "rgb(255,255,255)", null);
                File tempRotateFile = new File(tempRotatePath);
                tempRotateFile.delete();
            }
            p.setFileSize(oSize);
        } else {
            p.setFileSize(oSize);
            p.setWidth(imageWidth);
            p.setHeight(imageHeight);
            if (angle == 0) {
                try {
                    if (!targetFile.exists()){
                        FileUtils.copyFile(p.getTempFile(), targetFile);
                    }
                    if(p.getTempFile().exists()){   //删除临时文件
                        FileUtils.forceDeleteOnExit(p.getTempFile());
                    }
                } catch (IOException e) {
                    log.error(e.getMessage());
                    e.printStackTrace();
                    p.getTempFile().delete();
                    p.setTempFile(null);
                    return p;
                }
            } else {
                IMImageUtil im = new IMImageUtil();
                String tempRotatePath = "/tmp/rotate_" + UUID.randomUUID().toString();
                im.rotate(p.getTempFile().getPath(), tempRotatePath, angle, "rgb(255,255,255)", null);

                try {
                    FileUtils.moveFile(new File(tempRotatePath), targetFile);
                } catch (IOException e) {
                    log.error(e.getMessage());
                    e.printStackTrace();
                    p.getTempFile().delete();
                    p.setTempFile(null);
                    return p;
                }
            }
        }

        if (!targetFile.exists()) {
            log.error("save file error:" + p.getTempFile().getPath() + " targetFile:" + targetFile.getPath());
            p.setTitle("保存文件失败！");
            p.setFileName(p.getTempFile().getName());
            p.getTempFile().delete();
            p.setTempFile(null);
            return p;
        }

        // 插入到数据库，如果遇到exception就查一下是否已经存在相同的hashcode
        try {
            if (!isLostFile) {
                if (log.isDebugEnabled())
                    log.debug("before insert MD4:" + p.getHashcode());
                pictureService.insert(p);
            }
        } catch (Exception e) {
            log.error("Picture对象插入到数据库失败，正尝试查找是否存在相同的hashcode。hashcode="
                    + p.getHashcode() + " path=" + p.getPath() + "/" + p.getFileName());
            tempObj = pictureService.getByHashcode(p.getHashcode());
            if (tempObj != null) {
                p.setTempFile(null);
                log.debug("数据库中找到hashcode:" + tempObj.getHashcode());
                if (p.getTempFile() != null && p.getTempFile().exists())
                    p.getTempFile().delete();
                return tempObj;
            } else {
                log.debug("数据库中找不到hashcode:" + p.getHashcode());
                if (p.getTempFile() != null && p.getTempFile().exists())
                    p.getTempFile().delete();
                p = new Picture();
                p.setTitle("插入到数据库时出错！");
                targetFile.delete();
                return p;
            }
        }
        p.getTempFile().delete();
        p.setTempFile(null);
        return p;
    }


    /**
     * 根据exif标识获取图片需要旋转的角度
     *
     * @param pictureFile
     * @return
     */
    private double getNeededRotateAngle(File pictureFile) {
        if (log.isDebugEnabled()) {
            log.debug("getNeededRotateAngle:" + pictureFile.getPath());
        }
        double resultAngle = 0;
        int orientation = 0;
        try {
            Metadata metadata = ImageMetadataReader.readMetadata(pictureFile);
            if (metadata == null)
                return 0;
            Directory directory = metadata.getDirectory(ExifIFD0Directory.class);
            if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION))
                orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
            else
                return 0;
        } catch (ImageProcessingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MetadataException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        switch (orientation) {
            case 6:
                resultAngle = 90;
                break;
            case 3:
                resultAngle = 180;
                break;
            case 8:
                resultAngle = 270;
                break;
            default:
                break;
        }

        if (log.isDebugEnabled())
            log.debug("Angle:" + resultAngle);

        return resultAngle;
    }

    /**
     * 保存url列表中的全部图片
     *
     * @param urls 图片url列表
     * @return 键为url，值为picture对象
     */
    public Map<String, Picture> fetchPictureFromUrls(Set<String> urls, String title, String memo, Integer createUserId, Integer status) {
        Map<String, Picture> result = new HashMap<String, Picture>(urls.size());
        for (String url : urls) {
            result.put(url, fetchPictureFromUrl(url, title, memo, createUserId != null ? createUserId : 0, status));
        }
        return result;
    }

    /**
     * 保存远程图片到本地服务器，并返回图片对象
     *
     * @param urlStr 图片url
     * @return 图片对象，保存失败返回null
     */
    public Picture fetchPictureFromUrl(String urlStr, String title, String memo, Integer createUserId, Integer status) {
        return fetchPictureFromUrl(urlStr, title, memo, createUserId, status, "");
    }

    public Picture fetchPictureFromUrl(String urlStr, String title, String memo, Integer createUserId, Integer status, String referer) {
        if (StringUtil.isEmpty(urlStr)) {
            return null;
        }

        Picture p = null;
        try {
            Request hcReq = Request.Get(urlStr).connectTimeout(1500).socketTimeout(5000)
                    .addHeader("X-Forwarded-For", getRandomIP()).userAgent(getRandomUa())
                    .version(HttpVersion.HTTP_1_1);

            if (!StringUtil.isEmpty(referer)) {
                hcReq.addHeader("Referer", referer);
            }

            HttpResponse hcResp = null;
            try {
                hcResp = hcReq.execute().returnResponse();
            } catch (Exception e) {
                try {
                    hcResp = hcReq.execute().returnResponse();
                    log.warn("can not down after 1 retry! url:" + urlStr);
                } catch (Exception e2) {
                    HttpGet httpget = new HttpGet(urlStr);
                    httpget.addHeader("X-Forwarded-For", getRandomIP());
                    hcResp = httpClient.execute(httpget);
                    log.warn("can not down after 2 retry! url:" + urlStr);
                }
            }
            String extName ="jpeg";
            File tempFile = new File("/" + "tmp" + "/" + "fetch_" + UUID.randomUUID().toString() + "." + extName);
            if (hcResp.getStatusLine().getStatusCode() != 200) {
                log.warn("http status:" + hcResp.getStatusLine().getStatusCode() + " url:" + urlStr);
                return null;
            }
            hcResp.getEntity().writeTo(new FileOutputStream(tempFile));

            String fileName = urlStr.substring(urlStr.lastIndexOf("/") + 1);

            p = new Picture();
            p.setTitle(title != null ? title : fileName);
            p.setMemo(memo != null ? memo : fileName);
            p.setFileName(fileName);
            p.setExtName(extName);
            p.setTempFile(tempFile);
            p.setOriginalUrl(urlStr);
            p.setCreateUserId(createUserId);
            p.setModifyUserId(createUserId);
            if (status != null) {
                p.setStatus(status);
            } else {
                p.setStatus(Constants.STATUS_ENABLE);
            }

            p = savePictureFile(p);

            // 检查savePictureFile是否已经执行完整
            if (p.getTempFile() != null)
                return null;

        } catch (Exception e) {
            log.error("Fetch fail! url:" + urlStr+"\n"+e.toString());
            e.printStackTrace();
            return null;
        }

        return p;
    }

    private static String getRandomIP() {
        int number1 = new Random().nextInt(250) + 1;
        int number2 = new Random().nextInt(250) + 1;
        int number3 = new Random().nextInt(250) + 1;
        String ip = "183." + number1 + "." + number2 + "." + number3;
        return ip;
    }

    private static String getRandomUa() {
        String[] uas = {
                "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
                "Dalvik/1.6.0 (Linux; U; Android 4.1.2; HS-E956Q Build/JZO54K)",
                "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2_6 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8E200 Safari/6533.18.5 RPT-HTTPClient/0.3-3",
                "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; .NET4.0E) ",
                "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; 360space)",
                "Dalvik/1.6.0 (Linux; U; Android 4.1.2; HUAWEI A199 Build/HuaweiA199)"
        };
        return uas[new Random().nextInt(uas.length)];
    }

    /**
     * 拼凑图片路径
     * @param picture
     * @return
     */
    public String getPictureFullPath(Picture picture) {
        return pictureConfig.getSaveFilePath() + picture.getHost() + picture.getPath() + picture.getFileName();
    }

}
